summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLiam <byteslice@airmail.cc>2024-02-07 18:09:26 +0100
committerLiam <byteslice@airmail.cc>2024-02-07 18:15:01 +0100
commit6810929f6af54b35210153075e214738a013cae4 (patch)
treebaf2546dc79241ccbc5a40aec75a91979f452b86
parentservice: add os types and multi wait API (diff)
downloadyuzu-6810929f6af54b35210153075e214738a013cae4.tar
yuzu-6810929f6af54b35210153075e214738a013cae4.tar.gz
yuzu-6810929f6af54b35210153075e214738a013cae4.tar.bz2
yuzu-6810929f6af54b35210153075e214738a013cae4.tar.lz
yuzu-6810929f6af54b35210153075e214738a013cae4.tar.xz
yuzu-6810929f6af54b35210153075e214738a013cae4.tar.zst
yuzu-6810929f6af54b35210153075e214738a013cae4.zip
-rw-r--r--src/core/hle/service/server_manager.cpp438
-rw-r--r--src/core/hle/service/server_manager.h57
2 files changed, 234 insertions, 261 deletions
diff --git a/src/core/hle/service/server_manager.cpp b/src/core/hle/service/server_manager.cpp
index 8ef49387d..8c7f94c8c 100644
--- a/src/core/hle/service/server_manager.cpp
+++ b/src/core/hle/service/server_manager.cpp
@@ -20,50 +20,91 @@
namespace Service {
-constexpr size_t MaximumWaitObjects = 0x40;
-
-enum HandleType {
+enum class UserDataTag {
Port,
Session,
DeferEvent,
- Event,
};
-ServerManager::ServerManager(Core::System& system) : m_system{system}, m_serve_mutex{system} {
+class Port : public MultiWaitHolder, public Common::IntrusiveListBaseNode<Port> {
+public:
+ explicit Port(Kernel::KServerPort* server_port, SessionRequestHandlerFactory&& handler_factory)
+ : MultiWaitHolder(server_port), m_handler_factory(std::move(handler_factory)) {
+ this->SetUserData(static_cast<uintptr_t>(UserDataTag::Port));
+ }
+
+ ~Port() {
+ this->GetNativeHandle()->Close();
+ }
+
+ SessionRequestHandlerPtr CreateHandler() {
+ return m_handler_factory();
+ }
+
+private:
+ const SessionRequestHandlerFactory m_handler_factory;
+};
+
+class Session : public MultiWaitHolder, public Common::IntrusiveListBaseNode<Session> {
+public:
+ explicit Session(Kernel::KServerSession* server_session,
+ std::shared_ptr<SessionRequestManager>&& manager)
+ : MultiWaitHolder(server_session), m_manager(std::move(manager)) {
+ this->SetUserData(static_cast<uintptr_t>(UserDataTag::Session));
+ }
+
+ ~Session() {
+ this->GetNativeHandle()->Close();
+ }
+
+ std::shared_ptr<SessionRequestManager>& GetManager() {
+ return m_manager;
+ }
+
+ std::shared_ptr<HLERequestContext>& GetContext() {
+ return m_context;
+ }
+
+private:
+ std::shared_ptr<SessionRequestManager> m_manager;
+ std::shared_ptr<HLERequestContext> m_context;
+};
+
+ServerManager::ServerManager(Core::System& system) : m_system{system}, m_selection_mutex{system} {
// Initialize event.
- m_event = Kernel::KEvent::Create(system.Kernel());
- m_event->Initialize(nullptr);
+ m_wakeup_event = Kernel::KEvent::Create(system.Kernel());
+ m_wakeup_event->Initialize(nullptr);
// Register event.
- Kernel::KEvent::Register(system.Kernel(), m_event);
+ Kernel::KEvent::Register(system.Kernel(), m_wakeup_event);
+
+ // Link to holder.
+ m_wakeup_holder.emplace(std::addressof(m_wakeup_event->GetReadableEvent()));
+ m_wakeup_holder->LinkToMultiWait(std::addressof(m_deferred_list));
}
ServerManager::~ServerManager() {
// Signal stop.
m_stop_source.request_stop();
- m_event->Signal();
+ m_wakeup_event->Signal();
// Wait for processing to stop.
m_stopped.Wait();
m_threads.clear();
- // Clean up server ports.
- for (const auto& [port, handler] : m_ports) {
- port->Close();
+ // Clean up ports.
+ for (auto it = m_servers.begin(); it != m_servers.end(); it = m_servers.erase(it)) {
+ delete std::addressof(*it);
}
// Clean up sessions.
- for (const auto& [session, manager] : m_sessions) {
- session->Close();
- }
-
- for (const auto& request : m_deferrals) {
- request.session->Close();
+ for (auto it = m_sessions.begin(); it != m_sessions.end(); it = m_sessions.erase(it)) {
+ delete std::addressof(*it);
}
- // Close event.
- m_event->GetReadableEvent().Close();
- m_event->Close();
+ // Close wakeup event.
+ m_wakeup_event->GetReadableEvent().Close();
+ m_wakeup_event->Close();
if (m_deferral_event) {
m_deferral_event->GetReadableEvent().Close();
@@ -75,19 +116,19 @@ void ServerManager::RunServer(std::unique_ptr<ServerManager>&& server_manager) {
server_manager->m_system.RunServer(std::move(server_manager));
}
-Result ServerManager::RegisterSession(Kernel::KServerSession* session,
+Result ServerManager::RegisterSession(Kernel::KServerSession* server_session,
std::shared_ptr<SessionRequestManager> manager) {
- ASSERT(m_sessions.size() + m_ports.size() < MaximumWaitObjects);
-
// We are taking ownership of the server session, so don't open it.
+ auto* session = new Session(server_session, std::move(manager));
+
// Begin tracking the server session.
{
- std::scoped_lock ll{m_list_mutex};
- m_sessions.emplace(session, std::move(manager));
+ std::scoped_lock ll{m_deferred_list_mutex};
+ m_sessions.push_back(*session);
}
- // Signal the wakeup event.
- m_event->Signal();
+ // Register to wait on the session.
+ this->LinkToDeferredList(session);
R_SUCCEED();
}
@@ -95,21 +136,22 @@ Result ServerManager::RegisterSession(Kernel::KServerSession* session,
Result ServerManager::RegisterNamedService(const std::string& service_name,
SessionRequestHandlerFactory&& handler_factory,
u32 max_sessions) {
- ASSERT(m_sessions.size() + m_ports.size() < MaximumWaitObjects);
-
// Add the new server to sm: and get the moved server port.
Kernel::KServerPort* server_port{};
R_ASSERT(m_system.ServiceManager().RegisterService(std::addressof(server_port), service_name,
max_sessions, handler_factory));
+ // We are taking ownership of the server port, so don't open it.
+ auto* server = new Port(server_port, std::move(handler_factory));
+
// Begin tracking the server port.
{
- std::scoped_lock ll{m_list_mutex};
- m_ports.emplace(server_port, std::move(handler_factory));
+ std::scoped_lock ll{m_deferred_list_mutex};
+ m_servers.push_back(*server);
}
- // Signal the wakeup event.
- m_event->Signal();
+ // Register to wait on the server port.
+ this->LinkToDeferredList(server);
R_SUCCEED();
}
@@ -127,8 +169,6 @@ Result ServerManager::RegisterNamedService(const std::string& service_name,
Result ServerManager::ManageNamedPort(const std::string& service_name,
SessionRequestHandlerFactory&& handler_factory,
u32 max_sessions) {
- ASSERT(m_sessions.size() + m_ports.size() < MaximumWaitObjects);
-
// Create a new port.
auto* port = Kernel::KPort::Create(m_system.Kernel());
port->Initialize(max_sessions, false, 0);
@@ -149,12 +189,18 @@ Result ServerManager::ManageNamedPort(const std::string& service_name,
// Open a new reference to the server port.
port->GetServerPort().Open();
- // Begin tracking the server port.
+ // Transfer ownership into a new port object.
+ auto* server = new Port(std::addressof(port->GetServerPort()), std::move(handler_factory));
+
+ // Begin tracking the port.
{
- std::scoped_lock ll{m_list_mutex};
- m_ports.emplace(std::addressof(port->GetServerPort()), std::move(handler_factory));
+ std::scoped_lock ll{m_deferred_list_mutex};
+ m_servers.push_back(*server);
}
+ // Register to wait on the port.
+ this->LinkToDeferredList(server);
+
// We succeeded.
R_SUCCEED();
}
@@ -173,6 +219,11 @@ Result ServerManager::ManageDeferral(Kernel::KEvent** out_event) {
// Set the output.
*out_event = m_deferral_event;
+ // Register to wait on the event.
+ m_deferral_holder.emplace(std::addressof(m_deferral_event->GetReadableEvent()));
+ m_deferral_holder->SetUserData(static_cast<uintptr_t>(UserDataTag::DeferEvent));
+ this->LinkToDeferredList(std::addressof(*m_deferral_holder));
+
// We succeeded.
R_SUCCEED();
}
@@ -191,270 +242,185 @@ Result ServerManager::LoopProcess() {
R_RETURN(this->LoopProcessImpl());
}
-Result ServerManager::LoopProcessImpl() {
- while (!m_stop_source.stop_requested()) {
- R_TRY(this->WaitAndProcessImpl());
+void ServerManager::LinkToDeferredList(MultiWaitHolder* holder) {
+ // Link.
+ {
+ std::scoped_lock lk{m_deferred_list_mutex};
+ holder->LinkToMultiWait(std::addressof(m_deferred_list));
}
- R_SUCCEED();
+ // Signal the wakeup event.
+ m_wakeup_event->Signal();
}
-Result ServerManager::WaitAndProcessImpl() {
- Kernel::KScopedAutoObject<Kernel::KSynchronizationObject> wait_obj;
- HandleType wait_type{};
+void ServerManager::LinkDeferred() {
+ std::scoped_lock lk{m_deferred_list_mutex};
+ m_multi_wait.MoveAll(std::addressof(m_deferred_list));
+}
+MultiWaitHolder* ServerManager::WaitSignaled() {
// Ensure we are the only thread waiting for this server.
- std::unique_lock sl{m_serve_mutex};
+ std::scoped_lock lk{m_selection_mutex};
- // If we're done, return before we start waiting.
- R_SUCCEED_IF(m_stop_source.stop_requested());
+ while (true) {
+ this->LinkDeferred();
- // Wait for a tracked object to become signaled.
- {
- s32 num_objs{};
- std::array<HandleType, MaximumWaitObjects> wait_types{};
- std::array<Kernel::KSynchronizationObject*, MaximumWaitObjects> wait_objs{};
-
- const auto AddWaiter{
- [&](Kernel::KSynchronizationObject* synchronization_object, HandleType type) {
- // Open a new reference to the object.
- synchronization_object->Open();
-
- // Insert into the list.
- wait_types[num_objs] = type;
- wait_objs[num_objs++] = synchronization_object;
- }};
-
- {
- std::scoped_lock ll{m_list_mutex};
-
- // Add all of our ports.
- for (const auto& [port, handler] : m_ports) {
- AddWaiter(port, HandleType::Port);
- }
-
- // Add all of our sessions.
- for (const auto& [session, manager] : m_sessions) {
- AddWaiter(session, HandleType::Session);
- }
+ // If we're done, return before we start waiting.
+ if (m_stop_source.stop_requested()) {
+ return nullptr;
}
- // Add the deferral wakeup event.
- if (m_deferral_event != nullptr) {
- AddWaiter(std::addressof(m_deferral_event->GetReadableEvent()), HandleType::DeferEvent);
+ auto* selected = m_multi_wait.WaitAny(m_system.Kernel());
+ if (selected == std::addressof(*m_wakeup_holder)) {
+ // Clear and restart if we were woken up.
+ m_wakeup_event->Clear();
+ } else {
+ // Unlink and handle the event.
+ selected->UnlinkFromMultiWait();
+ return selected;
}
+ }
+}
- // Add the wakeup event.
- AddWaiter(std::addressof(m_event->GetReadableEvent()), HandleType::Event);
-
- // Clean up extra references on exit.
- SCOPE_EXIT({
- for (s32 i = 0; i < num_objs; i++) {
- wait_objs[i]->Close();
- }
- });
-
- // Wait for a signal.
- s32 out_index{-1};
- R_TRY_CATCH(Kernel::KSynchronizationObject::Wait(m_system.Kernel(), &out_index,
- wait_objs.data(), num_objs, -1)) {
- R_CATCH(Kernel::ResultSessionClosed) {
- // On session closed, index is updated and we don't want to return an error.
- }
- }
- R_END_TRY_CATCH;
- ASSERT(out_index >= 0 && out_index < num_objs);
+Result ServerManager::Process(MultiWaitHolder* holder) {
+ switch (static_cast<UserDataTag>(holder->GetUserData())) {
+ case UserDataTag::Session:
+ R_RETURN(this->OnSessionEvent(static_cast<Session*>(holder)));
+ case UserDataTag::Port:
+ R_RETURN(this->OnPortEvent(static_cast<Port*>(holder)));
+ case UserDataTag::DeferEvent:
+ R_RETURN(this->OnDeferralEvent());
+ default:
+ UNREACHABLE();
+ }
+}
- // Set the output index.
- wait_obj = wait_objs[out_index];
- wait_type = wait_types[out_index];
+bool ServerManager::WaitAndProcessImpl() {
+ if (auto* signaled_holder = this->WaitSignaled(); signaled_holder != nullptr) {
+ R_ASSERT(this->Process(signaled_holder));
+ return true;
+ } else {
+ return false;
}
+}
- // Process what we just received, temporarily removing the object so it is
- // not processed concurrently by another thread.
- {
- switch (wait_type) {
- case HandleType::Port: {
- // Port signaled.
- auto* port = wait_obj->DynamicCast<Kernel::KServerPort*>();
- SessionRequestHandlerFactory handler_factory;
-
- // Remove from tracking.
- {
- std::scoped_lock ll{m_list_mutex};
- ASSERT(m_ports.contains(port));
- m_ports.at(port).swap(handler_factory);
- m_ports.erase(port);
- }
-
- // Allow other threads to serve.
- sl.unlock();
-
- // Finish.
- R_RETURN(this->OnPortEvent(port, std::move(handler_factory)));
- }
- case HandleType::Session: {
- // Session signaled.
- auto* session = wait_obj->DynamicCast<Kernel::KServerSession*>();
- std::shared_ptr<SessionRequestManager> manager;
-
- // Remove from tracking.
- {
- std::scoped_lock ll{m_list_mutex};
- ASSERT(m_sessions.contains(session));
- m_sessions.at(session).swap(manager);
- m_sessions.erase(session);
- }
-
- // Allow other threads to serve.
- sl.unlock();
-
- // Finish.
- R_RETURN(this->OnSessionEvent(session, std::move(manager)));
- }
- case HandleType::DeferEvent: {
- // Clear event.
- ASSERT(R_SUCCEEDED(m_deferral_event->Clear()));
-
- // Drain the list of deferrals while we process.
- std::list<RequestState> deferrals;
- {
- std::scoped_lock ll{m_list_mutex};
- m_deferrals.swap(deferrals);
- }
-
- // Allow other threads to serve.
- sl.unlock();
-
- // Finish.
- R_RETURN(this->OnDeferralEvent(std::move(deferrals)));
- }
- case HandleType::Event: {
- // Clear event and finish.
- R_RETURN(m_event->Clear());
- }
- default: {
- UNREACHABLE();
- }
- }
+Result ServerManager::LoopProcessImpl() {
+ while (!m_stop_source.stop_requested()) {
+ this->WaitAndProcessImpl();
}
+
+ R_SUCCEED();
}
-Result ServerManager::OnPortEvent(Kernel::KServerPort* port,
- SessionRequestHandlerFactory&& handler_factory) {
+Result ServerManager::OnPortEvent(Port* server) {
// Accept a new server session.
- Kernel::KServerSession* session = port->AcceptSession();
- ASSERT(session != nullptr);
+ auto* server_port = static_cast<Kernel::KServerPort*>(server->GetNativeHandle());
+ Kernel::KServerSession* server_session = server_port->AcceptSession();
+ ASSERT(server_session != nullptr);
// Create the session manager and install the handler.
auto manager = std::make_shared<SessionRequestManager>(m_system.Kernel(), *this);
- manager->SetSessionHandler(handler_factory());
+ manager->SetSessionHandler(server->CreateHandler());
- // Track the server session.
- {
- std::scoped_lock ll{m_list_mutex};
- m_ports.emplace(port, std::move(handler_factory));
- m_sessions.emplace(session, std::move(manager));
- }
+ // Create and register the new session.
+ this->RegisterSession(server_session, std::move(manager));
- // Signal the wakeup event.
- m_event->Signal();
+ // Resume tracking the port.
+ this->LinkToDeferredList(server);
// We succeeded.
R_SUCCEED();
}
-Result ServerManager::OnSessionEvent(Kernel::KServerSession* session,
- std::shared_ptr<SessionRequestManager>&& manager) {
- Result rc{ResultSuccess};
+Result ServerManager::OnSessionEvent(Session* session) {
+ Result res = ResultSuccess;
// Try to receive a message.
- std::shared_ptr<HLERequestContext> context;
- rc = session->ReceiveRequestHLE(&context, manager);
+ auto* server_session = static_cast<Kernel::KServerSession*>(session->GetNativeHandle());
+ res = server_session->ReceiveRequestHLE(&session->GetContext(), session->GetManager());
// If the session has been closed, we're done.
- if (rc == Kernel::ResultSessionClosed) {
- // Close the session.
- session->Close();
-
- // Finish.
+ if (res == Kernel::ResultSessionClosed) {
+ this->DestroySession(session);
R_SUCCEED();
}
- ASSERT(R_SUCCEEDED(rc));
- RequestState request{
- .session = session,
- .context = std::move(context),
- .manager = std::move(manager),
- };
+ R_ASSERT(res);
// Complete the sync request with deferral handling.
- R_RETURN(this->CompleteSyncRequest(std::move(request)));
+ R_RETURN(this->CompleteSyncRequest(session));
}
-Result ServerManager::CompleteSyncRequest(RequestState&& request) {
- Result rc{ResultSuccess};
- Result service_rc{ResultSuccess};
+Result ServerManager::CompleteSyncRequest(Session* session) {
+ Result res = ResultSuccess;
+ Result service_res = ResultSuccess;
// Mark the request as not deferred.
- request.context->SetIsDeferred(false);
+ session->GetContext()->SetIsDeferred(false);
// Complete the request. We have exclusive access to this session.
- service_rc = request.manager->CompleteSyncRequest(request.session, *request.context);
+ auto* server_session = static_cast<Kernel::KServerSession*>(session->GetNativeHandle());
+ service_res =
+ session->GetManager()->CompleteSyncRequest(server_session, *session->GetContext());
// If we've been deferred, we're done.
- if (request.context->GetIsDeferred()) {
- // Insert into deferral list.
- std::scoped_lock ll{m_list_mutex};
- m_deferrals.emplace_back(std::move(request));
+ if (session->GetContext()->GetIsDeferred()) {
+ // Insert into deferred session list.
+ std::scoped_lock ll{m_deferred_list_mutex};
+ m_deferred_sessions.push_back(session);
// Finish.
R_SUCCEED();
}
// Send the reply.
- rc = request.session->SendReplyHLE();
+ res = server_session->SendReplyHLE();
// If the session has been closed, we're done.
- if (rc == Kernel::ResultSessionClosed || service_rc == IPC::ResultSessionClosed) {
- // Close the session.
- request.session->Close();
-
- // Finish.
+ if (res == Kernel::ResultSessionClosed || service_res == IPC::ResultSessionClosed) {
+ this->DestroySession(session);
R_SUCCEED();
}
- ASSERT(R_SUCCEEDED(rc));
- ASSERT(R_SUCCEEDED(service_rc));
-
- // Reinsert the session.
- {
- std::scoped_lock ll{m_list_mutex};
- m_sessions.emplace(request.session, std::move(request.manager));
- }
+ R_ASSERT(res);
+ R_ASSERT(service_res);
- // Signal the wakeup event.
- m_event->Signal();
+ // We succeeded, so we can process future messages on this session.
+ this->LinkToDeferredList(session);
- // We succeeded.
R_SUCCEED();
}
-Result ServerManager::OnDeferralEvent(std::list<RequestState>&& deferrals) {
- ON_RESULT_FAILURE {
- std::scoped_lock ll{m_list_mutex};
- m_deferrals.splice(m_deferrals.end(), deferrals);
- };
+Result ServerManager::OnDeferralEvent() {
+ // Clear event before grabbing the list.
+ m_deferral_event->Clear();
- while (!deferrals.empty()) {
- RequestState request = deferrals.front();
- deferrals.pop_front();
+ // Get and clear list.
+ const auto deferrals = [&] {
+ std::scoped_lock lk{m_deferred_list_mutex};
+ return std::move(m_deferred_sessions);
+ }();
- // Try again to complete the request.
- R_TRY(this->CompleteSyncRequest(std::move(request)));
+ // Relink deferral event.
+ this->LinkToDeferredList(std::addressof(*m_deferral_holder));
+
+ // For each session, try again to complete the request.
+ for (auto* session : deferrals) {
+ R_ASSERT(this->CompleteSyncRequest(session));
}
R_SUCCEED();
}
+void ServerManager::DestroySession(Session* session) {
+ // Unlink.
+ {
+ std::scoped_lock lk{m_deferred_list_mutex};
+ m_sessions.erase(m_sessions.iterator_to(*session));
+ }
+
+ // Free the session.
+ delete session;
+}
+
} // namespace Service
diff --git a/src/core/hle/service/server_manager.h b/src/core/hle/service/server_manager.h
index 7481c8521..5173ce46e 100644
--- a/src/core/hle/service/server_manager.h
+++ b/src/core/hle/service/server_manager.h
@@ -3,17 +3,16 @@
#pragma once
-#include <functional>
#include <list>
-#include <map>
#include <mutex>
-#include <string_view>
+#include <optional>
#include <vector>
#include "common/polyfill_thread.h"
#include "common/thread.h"
#include "core/hle/result.h"
#include "core/hle/service/hle_ipc.h"
+#include "core/hle/service/os/multi_wait.h"
#include "core/hle/service/os/mutex.h"
namespace Core {
@@ -24,11 +23,13 @@ namespace Kernel {
class KEvent;
class KServerPort;
class KServerSession;
-class KSynchronizationObject;
} // namespace Kernel
namespace Service {
+class Port;
+class Session;
+
class ServerManager {
public:
explicit ServerManager(Core::System& system);
@@ -52,34 +53,40 @@ public:
static void RunServer(std::unique_ptr<ServerManager>&& server);
private:
- struct RequestState;
-
+ void LinkToDeferredList(MultiWaitHolder* holder);
+ void LinkDeferred();
+ MultiWaitHolder* WaitSignaled();
+ Result Process(MultiWaitHolder* holder);
+ bool WaitAndProcessImpl();
Result LoopProcessImpl();
- Result WaitAndProcessImpl();
- Result OnPortEvent(Kernel::KServerPort* port, SessionRequestHandlerFactory&& handler_factory);
- Result OnSessionEvent(Kernel::KServerSession* session,
- std::shared_ptr<SessionRequestManager>&& manager);
- Result OnDeferralEvent(std::list<RequestState>&& deferrals);
- Result CompleteSyncRequest(RequestState&& state);
+
+ Result OnPortEvent(Port* port);
+ Result OnSessionEvent(Session* session);
+ Result OnDeferralEvent();
+ Result CompleteSyncRequest(Session* session);
+
+private:
+ void DestroySession(Session* session);
private:
Core::System& m_system;
- Mutex m_serve_mutex;
- std::mutex m_list_mutex;
+ Mutex m_selection_mutex;
- // Guest state tracking
- std::map<Kernel::KServerPort*, SessionRequestHandlerFactory> m_ports{};
- std::map<Kernel::KServerSession*, std::shared_ptr<SessionRequestManager>> m_sessions{};
- Kernel::KEvent* m_event{};
+ // Events
+ Kernel::KEvent* m_wakeup_event{};
Kernel::KEvent* m_deferral_event{};
- // Deferral tracking
- struct RequestState {
- Kernel::KServerSession* session;
- std::shared_ptr<HLERequestContext> context;
- std::shared_ptr<SessionRequestManager> manager;
- };
- std::list<RequestState> m_deferrals{};
+ // Deferred wait list
+ std::mutex m_deferred_list_mutex{};
+ MultiWait m_deferred_list{};
+
+ // Guest state tracking
+ MultiWait m_multi_wait{};
+ Common::IntrusiveListBaseTraits<Port>::ListType m_servers{};
+ Common::IntrusiveListBaseTraits<Session>::ListType m_sessions{};
+ std::list<Session*> m_deferred_sessions{};
+ std::optional<MultiWaitHolder> m_wakeup_holder{};
+ std::optional<MultiWaitHolder> m_deferral_holder{};
// Host state tracking
Common::Event m_stopped{};